{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from __future__ import absolute_import\n",
    "from __future__ import division\n",
    "from __future__ import print_function\n",
    "\n",
    "import collections\n",
    "import math\n",
    "import os\n",
    "import random\n",
    "import zipfile\n",
    "\n",
    "import numpy as np\n",
    "from six.moves import urllib\n",
    "from six.moves import xrange  # pylint: disable=redefined-builtin\n",
    "import tensorflow as tf\n",
    "from tensorflow.python.client import device_lib\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['/gpu:0']"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "local_device_protos = device_lib.list_local_devices()\n",
    "[x.name for x in local_device_protos if x.device_type == 'GPU']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "path_to_data=\"../../data/rcv1/string/out/reuters-rcv1-full-tokenized.txt\"\n",
    "\n",
    "with open(path_to_data) as f:\n",
    "    words = tf.compat.as_str(f.read()).split()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "print('Data size', len(words))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Step 2: Build the dictionary and replace rare words with UNK token.\n",
    "vocabulary_size = 50000\n",
    "\n",
    "def build_dataset(words):\n",
    "    count = [['UNK', -1]]\n",
    "    count.extend(collections.Counter(words).most_common(vocabulary_size - 1))\n",
    "    dictionary = dict()\n",
    "\n",
    "    for word, _ in count:\n",
    "        dictionary[word] = len(dictionary)\n",
    "  \n",
    "    data = list()\n",
    "    unk_count = 0\n",
    "  \n",
    "    for word in words:\n",
    "        if word in dictionary:\n",
    "            index = dictionary[word]\n",
    "        else:\n",
    "            index = 0  # dictionary['UNK']\n",
    "            unk_count += 1\n",
    "        data.append(index)\n",
    "    \n",
    "    count[0][1] = unk_count\n",
    "    reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))\n",
    "    \n",
    "    return data, count, dictionary, reverse_dictionary"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "data, count, dictionary, reverse_dictionary = build_dataset(words)\n",
    "del words  # Hint to reduce memory.\n",
    "print('Most common words (+UNK)', count[:5])\n",
    "print('Sample data', data[:10], [reverse_dictionary[i] for i in data[:10]])\n",
    "\n",
    "data_index = 0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Step 3: Function to generate a training batch for the skip-gram model.\n",
    "def generate_batch(batch_size, num_skips, skip_window):\n",
    "    global data_index\n",
    "    \n",
    "    assert batch_size % num_skips == 0\n",
    "    assert num_skips <= 2 * skip_window\n",
    "  \n",
    "    batch = np.ndarray(shape=(batch_size), dtype=np.int32)\n",
    "    labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)\n",
    "    \n",
    "    span = 2 * skip_window + 1  # [ skip_window target skip_window ]\n",
    "    buffer = collections.deque(maxlen=span)\n",
    "    \n",
    "    for _ in range(span):\n",
    "        buffer.append(data[data_index])\n",
    "        data_index = (data_index + 1) % len(data)\n",
    "  \n",
    "    for i in range(batch_size // num_skips):\n",
    "        target = skip_window  # target label at the center of the buffer\n",
    "        targets_to_avoid = [skip_window]\n",
    "    \n",
    "        for j in range(num_skips):\n",
    "            while target in targets_to_avoid:\n",
    "                target = random.randint(0, span - 1)\n",
    "           \n",
    "            targets_to_avoid.append(target)\n",
    "            batch[i * num_skips + j] = buffer[skip_window]\n",
    "            labels[i * num_skips + j, 0] = buffer[target]\n",
    "            \n",
    "        buffer.append(data[data_index])\n",
    "        data_index = (data_index + 1) % len(data)\n",
    "        \n",
    "    return batch, labels"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "batch, labels = generate_batch(batch_size=8, num_skips=2, skip_window=1)\n",
    "for i in range(8):\n",
    "    print(batch[i], reverse_dictionary[batch[i]],\n",
    "        '->', labels[i, 0], reverse_dictionary[labels[i, 0]])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# Step 4: Build and train a skip-gram model.\n",
    "\n",
    "batch_size = 128\n",
    "embedding_size = 128  # Dimension of the embedding vector.\n",
    "skip_window = 1       # How many words to consider left and right.\n",
    "num_skips = 2         # How many times to reuse an input to generate a label."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# We pick a random validation set to sample nearest neighbors. Here we limit the\n",
    "# validation samples to the words that have a low numeric ID, which by\n",
    "# construction are also the most frequent.\n",
    "valid_size = 16     # Random set of words to evaluate similarity on.\n",
    "valid_window = 100  # Only pick dev samples in the head of the distribution.\n",
    "valid_examples = np.random.choice(valid_window, valid_size, replace=False)\n",
    "num_sampled = 64    # Number of negative examples to sample."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "graph = tf.Graph()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "with graph.as_default():\n",
    "\n",
    "    # Input data.\n",
    "    train_inputs = tf.placeholder(tf.int32, shape=[batch_size])\n",
    "    train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])\n",
    "    valid_dataset = tf.constant(valid_examples, dtype=tf.int32)\n",
    "\n",
    "    # Ops and variables pinned to the CPU because of missing GPU implementation\n",
    "    with tf.device('/gpu:0'):\n",
    "        # Look up embeddings for inputs.\n",
    "        embeddings = tf.Variable(\n",
    "            tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))\n",
    "        embed = tf.nn.embedding_lookup(embeddings, train_inputs)\n",
    "\n",
    "        # Construct the variables for the NCE loss\n",
    "        nce_weights = tf.Variable(\n",
    "            tf.truncated_normal([vocabulary_size, embedding_size],\n",
    "                            stddev=1.0 / math.sqrt(embedding_size)))\n",
    "        nce_biases = tf.Variable(tf.zeros([vocabulary_size]))\n",
    "\n",
    "        # Compute the average NCE loss for the batch.\n",
    "        # tf.nce_loss automatically draws a new sample of the negative labels each\n",
    "        # time we evaluate the loss.\n",
    "    loss = tf.reduce_mean(\n",
    "        tf.nn.nce_loss(weights=nce_weights,\n",
    "                         biases=nce_biases,\n",
    "                         labels=train_labels,\n",
    "                         inputs=embed,\n",
    "                         num_sampled=num_sampled,\n",
    "                         num_classes=vocabulary_size))\n",
    "\n",
    "    # Construct the SGD optimizer using a learning rate of 1.0.\n",
    "    optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)\n",
    "\n",
    "    # Compute the cosine similarity between minibatch examples and all embeddings.\n",
    "    norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))\n",
    "    normalized_embeddings = embeddings / norm\n",
    "    valid_embeddings = tf.nn.embedding_lookup(\n",
    "        normalized_embeddings, valid_dataset)\n",
    "    similarity = tf.matmul(\n",
    "        valid_embeddings, normalized_embeddings, transpose_b=True)\n",
    "\n",
    "    # Add variable initializer.\n",
    "    init = tf.initialize_all_variables()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "num_steps = 100001"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "with tf.Session(config=tf.ConfigProto(log_device_placement=True), graph=graph) as session:\n",
    "      # We must initialize all variables before we use them.\n",
    "    init.run()\n",
    "    print(\"Initialized\")\n",
    "\n",
    "    average_loss = 0\n",
    "    for step in xrange(num_steps):\n",
    "        batch_inputs, batch_labels = generate_batch(\n",
    "            batch_size, num_skips, skip_window)\n",
    "        feed_dict = {train_inputs: batch_inputs, train_labels: batch_labels}\n",
    "\n",
    "        # We perform one update step by evaluating the optimizer op (including it\n",
    "        # in the list of returned values for session.run()\n",
    "        _, loss_val = session.run([optimizer, loss], feed_dict=feed_dict)\n",
    "        \n",
    "        average_loss += loss_val\n",
    "\n",
    "        if step % 2000 == 0:\n",
    "            if step > 0:\n",
    "                average_loss /= 2000\n",
    "            # The average loss is an estimate of the loss over the last 2000 batches.\n",
    "            print(\"Average loss at step \", step, \": \", average_loss)\n",
    "            average_loss = 0\n",
    "\n",
    "        # Note that this is expensive (~20% slowdown if computed every 500 steps)\n",
    "        if step % 10000 == 0:\n",
    "            sim = similarity.eval()\n",
    "            for i in xrange(valid_size):\n",
    "                valid_word = reverse_dictionary[valid_examples[i]]\n",
    "                top_k = 8  # number of nearest neighbors\n",
    "                nearest = (-sim[i, :]).argsort()[1:top_k + 1]\n",
    "                log_str = \"Nearest to %s:\" % valid_word\n",
    "                for k in xrange(top_k):\n",
    "                    close_word = reverse_dictionary[nearest[k]]\n",
    "                    log_str = \"%s %s,\" % (log_str, close_word)\n",
    "                print(log_str)\n",
    "                \n",
    "    final_embeddings = normalized_embeddings.eval()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def plot_with_labels(low_dim_embs, labels, filename='tsne.png'):\n",
    "    assert low_dim_embs.shape[0] >= len(labels), \"More labels than embeddings\"\n",
    "    plt.figure(figsize=(18, 18))  # in inches\n",
    "    for i, label in enumerate(labels):\n",
    "        x, y = low_dim_embs[i, :]\n",
    "        plt.scatter(x, y)\n",
    "        plt.annotate(label,\n",
    "                     xy=(x, y),\n",
    "                     xytext=(5, 2),\n",
    "                     textcoords='offset points',\n",
    "                     ha='right',\n",
    "                     va='bottom')\n",
    "\n",
    "    plt.savefig(filename)\n",
    "\n",
    "    \n",
    "from sklearn.manifold import TSNE\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000)\n",
    "plot_only = 500\n",
    "low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only, :])\n",
    "labels = [reverse_dictionary[i] for i in xrange(plot_only)]\n",
    "plot_with_labels(low_dim_embs, labels)    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
